Skip to content

Harden CodexMobile pairing and mobile security#2

Open
phoenixray2000 wants to merge 2 commits into
flyyangX:mainfrom
phoenixray2000:security-hardening-upstream
Open

Harden CodexMobile pairing and mobile security#2
phoenixray2000 wants to merge 2 commits into
flyyangX:mainfrom
phoenixray2000:security-hardening-upstream

Conversation

@phoenixray2000
Copy link
Copy Markdown

@phoenixray2000 phoenixray2000 commented May 11, 2026

概要

本 PR 对 CodexMobile 的移动端私有访问链路做安全加固,并把已登录设备管理恢复到当前侧边栏设置浮窗中。

主要改动:

  • 将旧的固定配对码 / localStorage token 流程迁移为显式一次性配对请求、控制台短时验证码和 HttpOnly 可信设备 Cookie。
  • 增加局域网/私有网络配对边界、配对请求冷却、配对尝试限流、token TTL、token 轮换、设备撤销和登出能力。
  • 在侧边栏“设置”中恢复授权设备管理,可刷新设备列表、退出当前设备、撤销其它可信设备。
  • 修复设置浮窗滚动,确保连接设备较多时列表可以完整查看。
  • 后端未开启完全访问时,前端不再显示“完全访问”选项。
  • 修复权限模式切换,避免在完整模式和自动接受编辑之间切换后沿用错误参数。
  • 修复移动端配对页滚动,避免输入框被强制滚回页头而无法输入。
  • 支持私网 Host Origin,使 http://<LAN/Tailscale IP>:<port> 能正常配对和连接 WebSocket,同时继续拒绝恶意 Host/Origin 组合。
  • 远端撤销当前设备时,前端会停止 WebSocket 重连并返回配对页。
  • 补充配对流程、设备状态排序、请求安全、权限策略、上传/静态文件安全、集成认证和 WebSocket 撤销相关测试。

背景 / 根因

安全加固迁移过程中,项目已经部分从 Bearer/localStorage 认证迁移到 Cookie 可信设备认证,但仍有几条链路不一致:

  • 配对码可以连续请求,缺少前端可见和后端强制的冷却。
  • oldbase 中已有的设备管理功能没有接入当前侧边栏设置页。
  • 前端可能显示后端未允许的权限模式。
  • 移动端配对页继承了主应用的 viewport 锁定逻辑,导致输入框可能被滚回页头。
  • 同源校验没有覆盖正常的私网 IP / Tailscale IP 访问方式。
  • 设备被远端撤销后,WebSocket 关闭会被前端当成普通断线重连。

用户影响

  • 首次移动端绑定变为显式操作:只有点击“请求配对码”后,电脑控制台才会打印验证码。
  • 重复请求配对码会被冷却和限流。
  • 用户可以在设置中管理可信设备、撤销旧设备、退出当前设备。
  • 被撤销的设备会回到配对页,不会持续重连。
  • 局域网和 Tailscale 私网 IP 访问仍然可用。
  • “完全访问”只会在服务端明确允许时出现。

安全说明

  • 认证改为 Cookie 方案,使用 HttpOnlySameSite=Strict、可选 Secure、TTL 和 token 轮换。
  • Legacy Bearer 认证默认关闭,仅在迁移场景显式开启。
  • 配对默认仅允许局域网/私有网络发起。
  • Cross-origin POST 和 WebSocket upgrade 会拒绝不安全来源,同时允许当前私网 Host 的同源访问。
  • 公网访问要求 HTTPS,私有网络访问除外。
  • 本地文件、上传文件和静态文件服务增加了路径校验和测试覆盖。

验证

已运行:

node --test client/src/pairing-flow.test.mjs client/src/security-devices.test.mjs client/src/app-state.test.mjs server/auth.test.mjs server/security-options.test.mjs server/integration.test.mjs
npm.cmd run build

## Summary

This PR hardens CodexMobile for private mobile access and restores device management in the current sidebar-based UI.

Key changes:

  • Replaces the old fixed localStorage token pairing flow with explicit one-time pairing requests, short-lived console codes, and HttpOnly trusted-device cookies.
  • Adds LAN/private-network pairing boundaries, request cooldown, pairing attempt limits, token TTL, token rotation, device revocation, and logout support.
  • Restores authorized device management inside the sidebar Settings panel, including device list, refresh, current-device logout, and revoking other trusted devices.
  • Ensures the Settings panel scrolls correctly when many trusted devices are present.
  • Hides the full-access permission option unless the backend explicitly enables it.
  • Preserves correct permission switching between default, accept-edits, and full-access modes.
  • Fixes mobile pairing-page scrolling so the login input is not forced back under the header.
  • Adds private-host Origin handling so http://<LAN/Tailscale IP>:<port> can pair and connect WebSocket while hostile Host/Origin combinations remain rejected.
  • Handles remote device revocation by stopping WebSocket reconnect and returning the revoked client to the pairing screen.
  • Adds supporting tests for pairing flow, device sorting/state, request security, permission policy, upload/static safety, integration auth, and WebSocket revocation.

Root Cause

The security-hardening migration had partially moved CodexMobile from bearer/localStorage auth to cookie-backed trusted devices, but several UI and protocol paths were still inconsistent:

  • Pairing could be repeatedly requested without a user-visible or server-enforced cooldown.
  • Device management existed in oldbase but was not integrated into the new sidebar Settings panel.
  • The frontend could expose permission modes that the backend did not allow.
  • The mobile pairing page inherited app viewport locking behavior and could trap the input under the header.
  • Same-origin checks did not account for normal private-IP mobile access.
  • WebSocket revocation closed the socket but the client treated it like a transient disconnect.

User Impact

  • First-time mobile pairing is explicit and safer: the console code is printed only after clicking "request pairing code".
  • Repeated pairing requests are cooled down and rate-limited.
  • Users can manage trusted devices from Settings, revoke old devices, and log out the current device.
  • Revoked devices return to the pairing page instead of reconnecting forever.
  • LAN/Tailscale private-IP usage remains supported.
  • Full-access mode only appears when the server allows it.

Security Notes

  • Auth is now cookie-based with HttpOnly, SameSite=Strict, optional Secure, TTL, and rotation.
  • Legacy Bearer auth is disabled unless explicitly enabled for migration.
  • Pairing is LAN/private-network only by default.
  • Cross-origin POST and WebSocket upgrade checks reject unsafe origins while allowing the current private Host origin.
  • Public access requires HTTPS unless the request is from a private network.
  • Sensitive local file/upload/static serving paths have additional validation and tests.

Validation

Ran:

node --test client/src/pairing-flow.test.mjs client/src/security-devices.test.mjs client/src/app-state.test.mjs server/auth.test.mjs server/security-options.test.mjs server/integration.test.mjs
npm.cmd run build

Result:

  • All selected tests passed.
  • Production build passed.
  • Vite still reports the existing large chunk size warning.

@phoenixray2000 phoenixray2000 marked this pull request as ready for review May 11, 2026 11:03
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2eff7d81cf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/codex-runner.js Outdated
Comment on lines +85 to +86
return codexSandboxForPermissionMode(permissionMode, {
dangerFullAccessEnabled: process.env.CODEXMOBILE_ENABLE_DANGER_FULL_ACCESS === '1'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor all enabled values for danger-full-access

When CODEXMOBILE_ENABLE_DANGER_FULL_ACCESS is set to true, yes, or on, readSecurityOptions() enables the feature and the UI/chat service will accept bypassPermissions, but headless local Codex turns still call codexSandboxForPermissionMode() with dangerFullAccessEnabled false because this check only accepts the literal string 1. In those deployments, selecting the now-visible full-access mode is rejected or fails just as the local turn starts; use the same flag parsing as the rest of the server instead of comparing directly to '1'.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修复

@flyyangX
Copy link
Copy Markdown
Owner

已将部分安全策略逻辑融合进了最新版本中,感谢贡献

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants